package org.atlantis.uzw.base.http;

import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.utils.HttpClientUtils;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.util.EntityUtils;
import org.atlantis.uzw.base.http.HttpRequest.HttpMethod;
import org.atlantis.uzw.utils.DaemonSharedExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

public class HttpClient implements InitializingBean, DisposableBean {

	private static final Logger log = LoggerFactory.getLogger(HttpClient.class);

	private int connectTimeout = 600;
	private int readTimeout = 1000;
	private int maxConnect = 128;

	private PoolingClientConnectionManager connectManager;
	private DefaultHttpClient client;

	public void setConnectTimeout(int connectTimeout) {
		this.connectTimeout = connectTimeout;
	}

	public void setReadTimeout(int readTimeout) {
		this.readTimeout = readTimeout;
	}

	public void setMaxConnect(int maxConnect) {
		this.maxConnect = maxConnect;
	}

	public String send(HttpRequest request) {
		String content = null;
		HttpEntity httpEntity = null;
		int statusCode = -1;
		try {
			HttpUriRequest uriRequest = toRequest(request);
			HttpResponse httpResponse = client.execute(uriRequest);
			httpEntity = httpResponse.getEntity();
			statusCode = httpResponse.getStatusLine().getStatusCode();
			if (statusCode == HttpStatus.SC_OK) {
				content = EntityUtils.toString(httpEntity);
			}
		} catch (ConnectTimeoutException e) {
			throw new HttpClientException(String.format("Connect to target url: %s timeout: %sms", request.getUrl(),
					connectTimeout));
		} catch (SocketTimeoutException e) {
			throw new HttpClientException(
					String.format("Read from target url: %s timeout: %sms", request.getUrl(), readTimeout));
		} catch (Exception e) {
			throw new HttpClientException(String.format("Unknow exception: %s", e));
		} finally {
			if (httpEntity != null) EntityUtils.consumeQuietly(httpEntity);
		}

		if (statusCode != HttpStatus.SC_OK) throw new HttpClientException(String.format(
				"Target url: %s return error http code: %s", request.getUrl(), statusCode));

		return content;
	}

	private HttpUriRequest toRequest(HttpRequest request) throws Exception {
		if (request.getMethod() == HttpMethod.GET) {
			URIBuilder builder = new URIBuilder(request.getUrl());
			for (Entry<String, String> entry : request.getParams().entrySet()) {
				builder.addParameter(entry.getKey(), entry.getValue());
			}
			return new HttpGet(builder.build());
		}
		List<NameValuePair> pairs = new ArrayList<>();
		for (Entry<String, String> entry : request.getParams().entrySet()) {
			pairs.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
		}
		HttpEntity httpEntity = new UrlEncodedFormEntity(pairs, "UTF-8");
		HttpPost httpPost = new HttpPost(request.getUrl());
		httpPost.setEntity(httpEntity);
		return httpPost;
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		connectManager = new PoolingClientConnectionManager();
		if (maxConnect < 8) maxConnect = 8;
		connectManager.setMaxTotal(maxConnect);
		connectManager.setDefaultMaxPerRoute((int) (maxConnect * 0.8));

		client = new DefaultHttpClient(connectManager);

		HttpParams params = client.getParams();
		HttpConnectionParams.setConnectionTimeout(params, connectTimeout);
		HttpConnectionParams.setSoTimeout(params, readTimeout);

		DaemonSharedExecutor.timer.scheduleWithFixedDelay(new IdleConnectionCleaner(connectManager, readTimeout * 32), 10000,
				readTimeout * 17, TimeUnit.MILLISECONDS);

		log.info("HttpClient initialized with options: {connectTimeout={}, readTimeout={}, maxConnect={}}", connectTimeout,
				readTimeout, maxConnect);
	}

	@Override
	public void destroy() throws Exception {
		HttpClientUtils.closeQuietly(client);
		log.info("HttpClient destroyed");
	}

	private static class IdleConnectionCleaner implements Runnable {

		private ClientConnectionManager connectionManager;
		private long idleTime;

		public IdleConnectionCleaner(ClientConnectionManager connectionManager, long idleTime) {
			this.connectionManager = connectionManager;
			this.idleTime = idleTime;
		}

		@Override
		public void run() {
			connectionManager.closeExpiredConnections();
			connectionManager.closeIdleConnections(idleTime, TimeUnit.MILLISECONDS);
		}
	}
}
